package org.aspire.math.geom;

import org.aspire.data.Emptyable;
import org.aspire.math.geom.event.DimensionEvent;
import org.aspire.math.geom.event.DimensionListener;
import org.aspire.util.Cache;

/******************************************************************************
 * The <code>Dimension</code> class encapsulates the width and height data of
 * an object in floating point precision.
 * <p>
 * The values of the width and height must be non-negative floating point 
 * values. Otherwise the constructor or the setters will throw an exception.
 * <p>
 * An empty dimension can be created which contains width and height data of 
 * zero. 
 * 
 * @author JPACarabuena
 *****************************************************************************/
public class Dimension implements Emptyable {

	// ========================================================================
	// Constants
	// ========================================================================

	// ========================================================================
	// Static Methods
	// ========================================================================
	
	// ========================================================================
	// Fields
	// ========================================================================
	
	/**************************************************************************
	 * The width of the object.
	 *************************************************************************/
	protected float width;
	
	/**************************************************************************
	 * The height of the object.
	 *************************************************************************/
	protected float height;
	
	/**************************************************************************
	 * A cache of listeners for this dimension.
	 *************************************************************************/
	private Cache listeners;
	
	// ========================================================================
	// Constructors
	// ========================================================================
	
	/**************************************************************************
	 * Creates a dimension data with the specified width and height in floating
	 * point precision.
	 * 
	 * @param width		the width for this dimension data.
	 * @param height	the height for this dimension data.
	 *************************************************************************/
	public Dimension(float width, float height) {
		this.width = width;
		this.height = height;
		this.listeners = new Cache();
	}
	
	/**************************************************************************
	 * Creates a dimension data with the default width and height of 0 (zero)
	 * in floating point precision.
	 *************************************************************************/
	public Dimension() {
		this(0.0f, 0.0f);
	}
	
	// ========================================================================
	// Getters & Setters
	// ========================================================================
	
	/**************************************************************************
	 * Gets the width for this dimension data in floating point precision.
	 * 
	 * @return	how 'wide' the data is.
	 *************************************************************************/
	public float getWidth() {
		return this.width;
	}
	
	/**************************************************************************
	 * Gets the height for this dimension data in floating point precision.
	 * 
	 * @return	how 'high' the data is.
	 *************************************************************************/
	public float getHeight() {
		return this.height;
	}
	
	/**************************************************************************
	 * Gets the width for this dimension data in integer precision.
	 * 
	 * @return	how 'wide' the data is.
	 *************************************************************************/
	public int getWidthInt() {
		return Math.round(this.width);
	}
	
	/**************************************************************************
	 * Gets the height for this dimension data in integer precision.
	 * 
	 * @return	how 'high' the data is.
	 *************************************************************************/
	public int getHeightInt() {
		return Math.round(this.height);
	}
	
	/**************************************************************************
	 * Sets the width of this dimension data to the specified width.
	 * 
	 * @param width		the new width for this dimension data.
	 *************************************************************************/
	public void setWidth(float width) {
		this.triggerListeners(DimensionEvent.SRC_WIDTH,
				(int) (width-this.width), 0);
		this.width = width;
	}
	
	/**************************************************************************
	 * Sets the height of this dimension data to the specified height.
	 * 
	 * @param height	the new height for this dimension data.
	 *************************************************************************/
	public void setHeight(float height) {
		this.triggerListeners(DimensionEvent.SRC_HEIGHT, 0, 
				(int) (height - this.height));
		this.height = height;
	}
	
	/**************************************************************************
	 * Sets the size of this dimension data to the specified size.
	 * 
	 * @param size	the new size for this dimension data.
	 *************************************************************************/
	public void setSize(Dimension size) {
		this.setSize(size.width, size.height);
	}
	
	/**************************************************************************
	 * Sets the size of this dimension data to the specified width and height. 
	 * 
	 * @param width	the new width for this dimension data.
	 * @param height the new height for this dimension data.
	 *************************************************************************/
	public void setSize(float width, float height) {
		this.triggerListeners(DimensionEvent.SRC_BOTH,
				(int) (width-this.width), (int) (height-this.height));
		this.width = width;
		this.height = height;
	}
	
	// ========================================================================
	// Override Methods
	// ========================================================================
	
	@Override
	public Object clone() {
		return new Dimension(this.width, this.height);
	}
	
	@Override
	public boolean equals(Object object) {
		if(object instanceof Dimension) {
			return ((this.width == ((Dimension) object).width)
					&& (this.height == ((Dimension) object).height));
		}
		return false;
	}
	
	@Override
	public String toString() {
		return this.getClass() + "[width=" + this.width + ",height="
				+ this.height + "]";
	}
	
	@Override
	public void clear() {
		this.width = 0;
		this.height = 0;
	}
	
	@Override
	public boolean isEmpty() {
		return (this.width == 0 && this.height == 0);
	}
	
	// ========================================================================
	// Methods
	// ========================================================================
	
	/**************************************************************************
	 * Add the specified listener to this dimension.
	 * 
	 * @param listener the listener for this dimension.
	 *************************************************************************/
	public void addListener(DimensionListener listener) {
		this.listeners.insert(listener);
	}
	
	/**************************************************************************
	 * Remove the specified listener to this dimension.
	 * 
	 * @param listener	the listener for this dimension to be removed.
	 *************************************************************************/
	public void removeListener(DimensionListener listener) {
		this.listeners.remove(this.listeners.indexOf(listener));
	}
	
	/**************************************************************************
	 * Triggers event for the dimension listeners.
	 * 
	 * @param src	the source of change.
	 * @param deltaWidth	the change in width.
	 * @param deltaHeight	the change in heiogh
	 *************************************************************************/
	private void triggerListeners(int src, int deltaWidth, int deltaHeight) {
		int n = this.listeners.size();
		
		if(deltaWidth == 0 && deltaHeight == 0) return;
		
		if(deltaWidth >= 0 && deltaHeight >= 0) {
			for(int i=0; i<n; i++) {
				((DimensionListener) this.listeners.elementAt(i))
					.dimensionIncreased(new DimensionEvent(src, 
							deltaWidth, deltaHeight));
			}
		} else if(deltaWidth <= 0 && deltaHeight <= 0) {
			for(int i=0; i<n; i++) {
				((DimensionListener) this.listeners.elementAt(i))
					.dimensionDecreased(new DimensionEvent(src, 
							deltaWidth, deltaHeight));
			}
		} else {
			for(int i=0; i<n; i++) {
				((DimensionListener) this.listeners.elementAt(i))
					.dimensionChanged(new DimensionEvent(src, 
							deltaWidth, deltaHeight));
			}
		}
	}
	
	// ========================================================================
	// Inner & Anonymous Classes
	// ========================================================================
}